
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
import { VISUAL_DIMENSIONS } from '../../util/types.js';
import SeriesDimensionDefine from '../SeriesDimensionDefine.js';
import { createHashMap, defaults, each, extend, isObject, isString } from 'zrender/lib/core/util.js';
import { createSourceFromSeriesDataOption, isSourceInstance } from '../Source.js';
import { CtorInt32Array } from '../DataStore.js';
import { normalizeToArray } from '../../util/model.js';
import { BE_ORDINAL, guessOrdinal } from './sourceHelper.js';
import { createDimNameMap, ensureSourceDimNameMap, SeriesDataSchema, shouldOmitUnusedDimensions } from './SeriesDataSchema.js';
/**
 * For outside usage compat (like echarts-gl are using it).
 */

export function createDimensions(source, opt) {
	return prepareSeriesDataSchema(source, opt).dimensions;
}
/**
 * This method builds the relationship between:
 * + "what the coord sys or series requires (see `coordDimensions`)",
 * + "what the user defines (in `encode` and `dimensions`, see `opt.dimensionsDefine` and `opt.encodeDefine`)"
 * + "what the data source provids (see `source`)".
 *
 * Some guess strategy will be adapted if user does not define something.
 * If no 'value' dimension specified, the first no-named dimension will be
 * named as 'value'.
 *
 * @return The results are always sorted by `storeDimIndex` asc.
 */

export default function prepareSeriesDataSchema( // TODO: TYPE completeDimensions type
	source, opt) {
	if (!isSourceInstance(source)) {
		source = createSourceFromSeriesDataOption(source);
	}

	opt = opt || {};
	var sysDims = opt.coordDimensions || [];
	var dimsDef = opt.dimensionsDefine || source.dimensionsDefine || [];
	var coordDimNameMap = createHashMap();
	var resultList = [];
	var dimCount = getDimCount(source, sysDims, dimsDef, opt.dimensionsCount); // Try to ignore unused dimensions if sharing a high dimension datastore
	// 30 is an experience value.

	var omitUnusedDimensions = opt.canOmitUnusedDimensions && shouldOmitUnusedDimensions(dimCount);
	var isUsingSourceDimensionsDef = dimsDef === source.dimensionsDefine;
	var dataDimNameMap = isUsingSourceDimensionsDef ? ensureSourceDimNameMap(source) : createDimNameMap(dimsDef);
	var encodeDef = opt.encodeDefine;

	if (!encodeDef && opt.encodeDefaulter) {
		encodeDef = opt.encodeDefaulter(source, dimCount);
	}

	var encodeDefMap = createHashMap(encodeDef);
	var indicesMap = new CtorInt32Array(dimCount);

	for (var i = 0; i < indicesMap.length; i++) {
		indicesMap[i] = -1;
	}

	function getResultItem(dimIdx) {
		var idx = indicesMap[dimIdx];

		if (idx < 0) {
			var dimDefItemRaw = dimsDef[dimIdx];
			var dimDefItem = isObject(dimDefItemRaw) ? dimDefItemRaw : {
				name: dimDefItemRaw
			};
			var resultItem = new SeriesDimensionDefine();
			var userDimName = dimDefItem.name;

			if (userDimName != null && dataDimNameMap.get(userDimName) != null) {
				// Only if `series.dimensions` is defined in option
				// displayName, will be set, and dimension will be displayed vertically in
				// tooltip by default.
				resultItem.name = resultItem.displayName = userDimName;
			}

			dimDefItem.type != null && (resultItem.type = dimDefItem.type);
			dimDefItem.displayName != null && (resultItem.displayName = dimDefItem.displayName);
			var newIdx = resultList.length;
			indicesMap[dimIdx] = newIdx;
			resultItem.storeDimIndex = dimIdx;
			resultList.push(resultItem);
			return resultItem;
		}

		return resultList[idx];
	}

	if (!omitUnusedDimensions) {
		for (var i = 0; i < dimCount; i++) {
			getResultItem(i);
		}
	} // Set `coordDim` and `coordDimIndex` by `encodeDefMap` and normalize `encodeDefMap`.

	encodeDefMap.each(function (dataDimsRaw, coordDim) {
		var dataDims = normalizeToArray(dataDimsRaw).slice(); // Note: It is allowed that `dataDims.length` is `0`, e.g., options is
		// `{encode: {x: -1, y: 1}}`. Should not filter anything in
		// this case.

		if (dataDims.length === 1 && !isString(dataDims[0]) && dataDims[0] < 0) {
			encodeDefMap.set(coordDim, false);
			return;
		}

		var validDataDims = encodeDefMap.set(coordDim, []);
		each(dataDims, function (resultDimIdxOrName, idx) {
			// The input resultDimIdx can be dim name or index.
			var resultDimIdx = isString(resultDimIdxOrName) ? dataDimNameMap.get(resultDimIdxOrName) : resultDimIdxOrName;

			if (resultDimIdx != null && resultDimIdx < dimCount) {
				validDataDims[idx] = resultDimIdx;
				applyDim(getResultItem(resultDimIdx), coordDim, idx);
			}
		});
	}); // Apply templates and default order from `sysDims`.

	var availDimIdx = 0;
	each(sysDims, function (sysDimItemRaw) {
		var coordDim;
		var sysDimItemDimsDef;
		var sysDimItemOtherDims;
		var sysDimItem;

		if (isString(sysDimItemRaw)) {
			coordDim = sysDimItemRaw;
			sysDimItem = {};
		} else {
			sysDimItem = sysDimItemRaw;
			coordDim = sysDimItem.name;
			var ordinalMeta = sysDimItem.ordinalMeta;
			sysDimItem.ordinalMeta = null;
			sysDimItem = extend({}, sysDimItem);
			sysDimItem.ordinalMeta = ordinalMeta; // `coordDimIndex` should not be set directly.

			sysDimItemDimsDef = sysDimItem.dimsDef;
			sysDimItemOtherDims = sysDimItem.otherDims;
			sysDimItem.name = sysDimItem.coordDim = sysDimItem.coordDimIndex = sysDimItem.dimsDef = sysDimItem.otherDims = null;
		}

		var dataDims = encodeDefMap.get(coordDim); // negative resultDimIdx means no need to mapping.

		if (dataDims === false) {
			return;
		}

		dataDims = normalizeToArray(dataDims); // dimensions provides default dim sequences.

		if (!dataDims.length) {
			for (var i = 0; i < (sysDimItemDimsDef && sysDimItemDimsDef.length || 1); i++) {
				while (availDimIdx < dimCount && getResultItem(availDimIdx).coordDim != null) {
					availDimIdx++;
				}

				availDimIdx < dimCount && dataDims.push(availDimIdx++);
			}
		} // Apply templates.

		each(dataDims, function (resultDimIdx, coordDimIndex) {
			var resultItem = getResultItem(resultDimIdx); // Coordinate system has a higher priority on dim type than source.

			if (isUsingSourceDimensionsDef && sysDimItem.type != null) {
				resultItem.type = sysDimItem.type;
			}

			applyDim(defaults(resultItem, sysDimItem), coordDim, coordDimIndex);

			if (resultItem.name == null && sysDimItemDimsDef) {
				var sysDimItemDimsDefItem = sysDimItemDimsDef[coordDimIndex];
				!isObject(sysDimItemDimsDefItem) && (sysDimItemDimsDefItem = {
					name: sysDimItemDimsDefItem
				});
				resultItem.name = resultItem.displayName = sysDimItemDimsDefItem.name;
				resultItem.defaultTooltip = sysDimItemDimsDefItem.defaultTooltip;
			} // FIXME refactor, currently only used in case: {otherDims: {tooltip: false}}

			sysDimItemOtherDims && defaults(resultItem.otherDims, sysDimItemOtherDims);
		});
	});

	function applyDim(resultItem, coordDim, coordDimIndex) {
		if (VISUAL_DIMENSIONS.get(coordDim) != null) {
			resultItem.otherDims[coordDim] = coordDimIndex;
		} else {
			resultItem.coordDim = coordDim;
			resultItem.coordDimIndex = coordDimIndex;
			coordDimNameMap.set(coordDim, true);
		}
	} // Make sure the first extra dim is 'value'.

	var generateCoord = opt.generateCoord;
	var generateCoordCount = opt.generateCoordCount;
	var fromZero = generateCoordCount != null;
	generateCoordCount = generateCoord ? generateCoordCount || 1 : 0;
	var extra = generateCoord || 'value';

	function ifNoNameFillWithCoordName(resultItem) {
		if (resultItem.name == null) {
			// Duplication will be removed in the next step.
			resultItem.name = resultItem.coordDim;
		}
	} // Set dim `name` and other `coordDim` and other props.

	if (!omitUnusedDimensions) {
		for (var resultDimIdx = 0; resultDimIdx < dimCount; resultDimIdx++) {
			var resultItem = getResultItem(resultDimIdx);
			var coordDim = resultItem.coordDim;

			if (coordDim == null) {
				// TODO no need to generate coordDim for isExtraCoord?
				resultItem.coordDim = genCoordDimName(extra, coordDimNameMap, fromZero);
				resultItem.coordDimIndex = 0; // Series specified generateCoord is using out.

				if (!generateCoord || generateCoordCount <= 0) {
					resultItem.isExtraCoord = true;
				}

				generateCoordCount--;
			}

			ifNoNameFillWithCoordName(resultItem);

			if (resultItem.type == null && (guessOrdinal(source, resultDimIdx) === BE_ORDINAL.Must // Consider the case:
      // {
      //    dataset: {source: [
      //        ['2001', 123],
      //        ['2002', 456],
      //        ...
      //        ['The others', 987],
      //    ]},
      //    series: {type: 'pie'}
      // }
      // The first column should better be treated as a "ordinal" although it
      // might not be detected as an "ordinal" by `guessOrdinal`.
      || resultItem.isExtraCoord && (resultItem.otherDims.itemName != null || resultItem.otherDims.seriesName != null))) {
				resultItem.type = 'ordinal';
			}
		}
	} else {
		each(resultList, function (resultItem) {
			// PENDING: guessOrdinal or let user specify type: 'ordinal' manually?
			ifNoNameFillWithCoordName(resultItem);
		}); // Sort dimensions: there are some rule that use the last dim as label,
		// and for some latter travel process easier.

		resultList.sort(function (item0, item1) {
			return item0.storeDimIndex - item1.storeDimIndex;
		});
	}

	removeDuplication(resultList);
	return new SeriesDataSchema({
		source: source,
		dimensions: resultList,
		fullDimensionCount: dimCount,
		dimensionOmitted: omitUnusedDimensions
	});
}

function removeDuplication(result) {
	var duplicationMap = createHashMap();

	for (var i = 0; i < result.length; i++) {
		var dim = result[i];
		var dimOriginalName = dim.name;
		var count = duplicationMap.get(dimOriginalName) || 0;

		if (count > 0) {
			// Starts from 0.
			dim.name = dimOriginalName + (count - 1);
		}

		count++;
		duplicationMap.set(dimOriginalName, count);
	}
} // ??? TODO
// Originally detect dimCount by data[0]. Should we
// optimize it to only by sysDims and dimensions and encode.
// So only necessary dims will be initialized.
// But
// (1) custom series should be considered. where other dims
// may be visited.
// (2) sometimes user need to calculate bubble size or use visualMap
// on other dimensions besides coordSys needed.
// So, dims that is not used by system, should be shared in data store?

function getDimCount(source, sysDims, dimsDef, optDimCount) {
	// Note that the result dimCount should not small than columns count
	// of data, otherwise `dataDimNameMap` checking will be incorrect.
	var dimCount = Math.max(source.dimensionsDetectedCount || 1, sysDims.length, dimsDef.length, optDimCount || 0);
	each(sysDims, function (sysDimItem) {
		var sysDimItemDimsDef;

		if (isObject(sysDimItem) && (sysDimItemDimsDef = sysDimItem.dimsDef)) {
			dimCount = Math.max(dimCount, sysDimItemDimsDef.length);
		}
	});
	return dimCount;
}

function genCoordDimName(name, map, fromZero) {
	if (fromZero || map.hasKey(name)) {
		var i = 0;

		while (map.hasKey(name + i)) {
			i++;
		}

		name += i;
	}

	map.set(name, true);
	return name;
}